/*
 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { factory } from "./factory/nodeFactory"
import {
    BlockStatement,
    CallExpression,
    ClassDeclaration,
    ClassDefinition,
    EtsImportDeclaration,
    EtsScript,
    ExpressionStatement,
    FunctionDeclaration,
    FunctionSignature,
    MethodDefinition,
    ScriptFunction,
    StructDeclaration
} from "./types"
import {
    Es2pandaClassDefinitionModifiers,
    Es2pandaModifierFlags
} from "../generated/Es2pandaEnums"
import { nullptr } from "@koalaui/interop"
import { AstNode } from "./peers/AstNode"
import { MemberExpression } from "./to-be-generated/MemberExpression"

type Visitor = (node: AstNode) => AstNode

// TODO: rethink (remove as)
function nodeVisitor<T extends AstNode | undefined>(node: T, visitor: Visitor): T {
    if (node === undefined) {
        return node
    }
    return visitor(node) as T
}

// TODO: rethink (remove as)
function nodesVisitor<T extends AstNode, TIn extends readonly T[] | undefined>(nodes: TIn, visitor: Visitor): T[] | TIn {
    if (nodes === undefined) {
        return nodes
    }
    return nodes.map(node => visitor(node) as T)
}

export function visitEachChild(
    node: AstNode,
    visitor: Visitor
): AstNode {
    if (node instanceof EtsScript) {
        return factory.updateEtsScript(
            node,
            nodesVisitor(node.statements, visitor)
        )
    }
    if (node instanceof CallExpression) {
        return factory.updateCallExpression(
            node,
            nodeVisitor(node.expression, visitor),
            undefined, // TODO
            nodesVisitor(node.arguments, visitor),
            nodeVisitor(node.trailingBlock, visitor)
        )
    }
    if (node instanceof FunctionDeclaration) {
        return factory.updateFunctionDeclaration(
            node,
            nodeVisitor(node.scriptFunction, visitor),
            node.isAnon
        )
    }
    if (node instanceof BlockStatement) {
        return factory.updateBlock(
            node,
            nodesVisitor(node.statements, visitor)
        )
    }
    if (node instanceof ExpressionStatement) {
        return factory.updateExpressionStatement(
            node,
            nodeVisitor(node.expression, visitor)
        )
    }
    if (node instanceof ClassDeclaration) {
        return factory.updateClassDeclaration(
            node,
            nodeVisitor(node.definition, visitor)
        )
    }
    if (node instanceof StructDeclaration) {
        return factory.updateStructDeclaration(
            node,
            nodeVisitor(node.definition, visitor)
        )
    }
    if (node instanceof ClassDefinition) {
        // TODO: fix
        return factory.updateClassDefinition(
            node,
            nodeVisitor(node.name, visitor),
            nodesVisitor(node.members, visitor),
            // passModifiers(modifiers) | es2panda_ModifierFlags.MODIFIER_FLAGS_PUBLIC | es2panda_ModifierFlags.MODIFIER_FLAGS_STATIC,
            node.modifiers,
            // TODO: pass through modifiers
            // Passing NONE causes failure when proceeding to CHECKED.
            Es2pandaClassDefinitionModifiers.CLASS_DEFINITION_MODIFIERS_CLASS_DECL,
            nodeVisitor(node.typeParamsDecl, visitor),
            nodeVisitor(node.superClass, visitor),
        )
    }
    if (node instanceof MethodDefinition) {
        // TODO: fix
        return factory.updateMethodDefinition(
            node,
            node.kind,
            node.name,
            factory.createFunctionExpression(
                // TODO: maybe fix
                factory.updateScriptFunction(
                    node.scriptFunction,
                    nodeVisitor(node.scriptFunction.body, visitor),
                    FunctionSignature.create(
                        node.scriptFunction.typeParamsDecl,
                        node.scriptFunction.parameters,
                        // TODO: fix return type
                        nullptr
                    ),
                    0,
                    node.modifiers | Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC || Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC,
                    false,
                    node.name
                )
            ),
            node.modifiers,
            false
        )
    }
    if (node instanceof ScriptFunction) {
        return factory.updateScriptFunction(
            node,
            nodeVisitor(node.body, visitor),
            nullptr,
            node.scriptFunctionFlags,
            Es2pandaModifierFlags.MODIFIER_FLAGS_NONE,
            false,
            nodeVisitor(node.ident, visitor),
            nodesVisitor(node.parameters, visitor)
        )
    }
    if (node instanceof EtsImportDeclaration) {
        return factory.updateImportDeclaration(
            node,
            nodeVisitor(node.importSource, visitor),
            nodesVisitor(node.importSpecifiers, visitor),
            node.importKind,
            node.hasDecl
        )
    }
    if (node instanceof MemberExpression) {
        return factory.updateMemberExpression(
            node,
            nodeVisitor(node.object, visitor),
            nodeVisitor(node.property, visitor),
            node.kind,
            node.computed,
            node.optional
        )
    }

    // TODO
    return node
}
